/*
 Copyright 2009 Abdulla Abdurakhmanov (abdulla@latestbit.com)
 Original sources are available at www.latestbit.com

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.

 StdSymmetricEncrypter.cpp. Created on: 01.09.2009 16:42:31
 */

#include "smartobjects/encrypters/StdSymmetricEncrypter.hpp"

namespace smartobjects {

const std::string StdSymmetricEncrypter::ALGORITHM_NAME = "AES";
const unsigned long StdSymmetricEncrypter::KEY_LENGTH = 128 / 8;

StdSymmetricEncrypter::StdSymmetricEncrypter() : m_poKey(NULL) {
}

StdSymmetricEncrypter::~StdSymmetricEncrypter() {
	if(m_poKey) {
		m_poKey->release();
		m_poKey = NULL;
	}
}

void StdSymmetricEncrypter::initCipher( SecretKey* poKey ) {

	if(poKey->getKeyData()->getLen() !=KEY_LENGTH) {
		throw GeneralSecurityException(
			boost::format("Invalid key length for AES cipher is specified: %1%. Expected len: %2%")
				%poKey->getKeyData()->getLen() %KEY_LENGTH
		);
	}

	if(m_poKey) {
		m_poKey->release();
		m_poKey = NULL;
	}

	m_poKey = poKey;
	m_poKey->addRef();
	// int nBits = poKey->getKeyData()->getLen() * 8;
}

SecretKey* StdSymmetricEncrypter::generateSecret() {
	SecretKey* poResult = new SecretKey();
	poResult->getKeyData()->setLen(KEY_LENGTH);
	int nResultCode = RAND_bytes ( poResult->getKeyData()->getData(), KEY_LENGTH );
	if ( nResultCode != 1 ) {
		poResult->release();
		throw GeneralSecurityException(
			boost::format("Unable to generate random key data! Result code: %1%") %nResultCode
		);
	}
	return poResult;
}

SecretKey* StdSymmetricEncrypter::decodeSecretKey(ByteBuffer* poBuffer) {
	if( poBuffer->getLenFromPos() < KEY_LENGTH ) {
		throw GeneralSecurityException(
			boost::format("Invalid key size received: %1%") %poBuffer->getLenFromPos()
		);
	}
	SecretKey* poResult = new SecretKey();
	poResult->getKeyData()->append(poBuffer->getDataAtPos(), KEY_LENGTH);
	poBuffer->seek(KEY_LENGTH);
	return poResult;
}

void StdSymmetricEncrypter::encodeSecretKey( SecretKey* poKey, ByteBuffer* poBuffer ) {
	poBuffer->append ( poKey->getKeyData() );
}

void StdSymmetricEncrypter::encrypt ( ByteBuffer* poInputData, ByteBuffer* poOutput ) {
	boost::recursive_mutex::scoped_lock oLock(m_mxEncrypter);

	EVP_CIPHER_CTX_init(&m_oEncrypterCtx);

	try {
		int nResultCode = EVP_EncryptInit_ex(&m_oEncrypterCtx, EVP_aes_128_ecb(), NULL, m_poKey->getKeyData()->getData(), NULL);
		if(nResultCode!=1) {
			throw GeneralSecurityException(
				boost::format("AES encrypter cipher init failed! Result code: %1%.") %nResultCode
			);
		}

		int nOutputLen = 0;
		int nAvailableCapacity = poOutput->getAvailableCapacity();
		int nInputLen = poInputData->getLenFromPos();

		if( nInputLen + EVP_CIPHER_CTX_block_size(&m_oEncrypterCtx) > nAvailableCapacity ) {
			poOutput->setCapacity( poOutput->getCapacity() - nAvailableCapacity + nInputLen + EVP_CIPHER_CTX_block_size(&m_oEncrypterCtx) );
		}

		nResultCode = EVP_EncryptUpdate (
				&m_oEncrypterCtx,
				poOutput->getData()+poOutput->getLen(),
				&nOutputLen,
				poInputData->getDataAtPos(),
				poInputData->getLenFromPos()
		);

		if( nResultCode!=1 ) {
			throw GeneralSecurityException(
				boost::format("Invalid encrypting data!. Result code: %1%") %nResultCode
			);
		}

		poOutput->setLen(poOutput->getLen()+nOutputLen);
		poInputData->seek(nInputLen);

		nResultCode = EVP_EncryptFinal_ex (
				&m_oEncrypterCtx,
				poOutput->getData()+poOutput->getLen(),
				&nOutputLen
		);

		if ( nResultCode!=1 ) {
			throw GeneralSecurityException(
				boost::format("Invalid encrypting data!. Result code: %1%") %nResultCode
			);
		}
		poOutput->setLen(poOutput->getLen()+nOutputLen);
		EVP_CIPHER_CTX_cleanup(&m_oEncrypterCtx);
	}
	catch(const GeneralSecurityException&) {
		EVP_CIPHER_CTX_cleanup(&m_oEncrypterCtx);
		throw;
	}
}

void StdSymmetricEncrypter::decrypt(ByteBuffer* poEncryptedData, ByteBuffer* poOutput) {
	boost::recursive_mutex::scoped_lock oLock(m_mxDecrypter);

	EVP_CIPHER_CTX_init(&m_oDecrypterCtx);

	try {
		int nResultCode = EVP_DecryptInit_ex(&m_oDecrypterCtx, EVP_aes_128_ecb(), NULL, m_poKey->getKeyData()->getData(), NULL);

		if(nResultCode!=1) {
			throw GeneralSecurityException(
				boost::format("AES decrypter cipher init failed! Result code: %1%.") %nResultCode
			);
		}

		int nOutputLen = 0;
		int nAvailableCapacity = poOutput->getAvailableCapacity();
		int nInputLen = poEncryptedData->getLenFromPos();

		if( nInputLen > nAvailableCapacity ) {
			poOutput->setCapacity( poOutput->getCapacity() - nAvailableCapacity + nInputLen );
		}

		nResultCode = EVP_DecryptUpdate (
				&m_oDecrypterCtx,
				poOutput->getData()+poOutput->getLen(),
				&nOutputLen,
				poEncryptedData->getDataAtPos(),
				poEncryptedData->getLenFromPos()
		);

		if( nResultCode!=1 ) {
			throw GeneralSecurityException(
				boost::format("Invalid encrypting data!. Result code: %1%") %nResultCode
			);
		}

		poOutput->setLen(poOutput->getLen()+nOutputLen);
		poEncryptedData->seek(nInputLen);

		nResultCode = EVP_DecryptFinal_ex (
				&m_oDecrypterCtx,
				poOutput->getData()+poOutput->getLen(),
				&nOutputLen
		);

		if ( nResultCode!=1 ) {
			throw GeneralSecurityException(
				boost::format("Invalid encrypting data!. Result code: %1%") %nResultCode
			);
		}
		poOutput->setLen(poOutput->getLen()+nOutputLen);
		EVP_CIPHER_CTX_cleanup(&m_oDecrypterCtx);
	}
	catch(const GeneralSecurityException&) {
		EVP_CIPHER_CTX_cleanup(&m_oDecrypterCtx);
		throw;
	}
}

const std::string& StdSymmetricEncrypter::getAlgorithm() const {
	return ALGORITHM_NAME;
}

}
